module dgl.Matrix;

import str = std.string;
import std.math;

import dgl.Vector3;
import dgl.Quaternion;

struct Matrix
{
	private float[16] m_matrix;

	public float m11()
	{
		return m_matrix[0];
	}

	public float m12()
	{
		return m_matrix[1];
	}

	public float m13()
	{
		return m_matrix[2];
	}

	public float m14()
	{
		return m_matrix[3];
	}

	public float m21()
	{
		return m_matrix[4];
	}

	public float m22()
	{
		return m_matrix[5];
	}

	public float m23()
	{
		return m_matrix[6];
	}

	public float m24()
	{
		return m_matrix[7];
	}

	public float m31()
	{
		return m_matrix[8];
	}

	public float m32()
	{
		return m_matrix[9];
	}

	public float m33()
	{
		return m_matrix[10];
	}

	public float m34()
	{
		return m_matrix[11];
	}

	public float m41()
	{
		return m_matrix[12];
	}

	public float m42()
	{
		return m_matrix[13];
	}

	public float m43()
	{
		return m_matrix[14];
	}

	public float m44()
	{
		return m_matrix[15];
	}

	public void m11( float value )
	{
		m_matrix[0] = value;
	}

	public void m12( float value )
	{
		m_matrix[1] = value;
	}

	public void m13( float value )
	{
		m_matrix[2] = value;
	}

	public void m14( float value )
	{
		m_matrix[3] = value;
	}

	public void m21( float value )
	{
		m_matrix[4] = value;
	}

	public void m22( float value )
	{
		m_matrix[5] = value;
	}

	public void m23( float value )
	{
		m_matrix[6] = value;
	}

	public void m24( float value )
	{
		m_matrix[7] = value;
	}

	public void m31( float value )
	{
		m_matrix[8] = value;
	}

	public void m32( float value )
	{
		m_matrix[9] = value;
	}

	public void m33( float value )
	{
		m_matrix[10] = value;
	}

	public void m34( float value )
	{
		m_matrix[11] = value;
	}

	public void m41( float value )
	{
		m_matrix[12] = value;
	}

	public void m42( float value )
	{
		m_matrix[13] = value;
	}

	public void m43( float value )
	{
		m_matrix[14] = value;
	}

	public void m44( float value )
	{
		m_matrix[15] = value;
	}

	public float * ptr()
	{
		return m_matrix.ptr;
	}

	public static Matrix identity()
	{
		Matrix matrix;
		matrix.m11 = 1;
		matrix.m12 = 0;
		matrix.m13 = 0;
		matrix.m14 = 0;
		matrix.m21 = 0;
		matrix.m22 = 1;
		matrix.m23 = 0;
		matrix.m24 = 0;
		matrix.m31 = 0;
		matrix.m32 = 0;
		matrix.m33 = 1;
		matrix.m34 = 0;
		matrix.m41 = 0;
		matrix.m42 = 0;
		matrix.m43 = 0;
		matrix.m44 = 1;
		return matrix;
	}

	public static Matrix createTranslation( Vector3 position )
	{
		Matrix matrix;
		matrix.m11 = 1;
		matrix.m12 = 0;
		matrix.m13 = 0;
		matrix.m14 = 0;
		matrix.m21 = 0;
		matrix.m22 = 1;
		matrix.m23 = 0;
		matrix.m24 = 0;
		matrix.m31 = 0;
		matrix.m32 = 0;
		matrix.m33 = 1;
		matrix.m34 = 0;
		matrix.m41 = position.x;
		matrix.m42 = position.y;
		matrix.m43 = position.z;
		matrix.m44 = 1;
		return matrix;
	}

	public static Matrix createScale( Vector3 scales )
	{
		Matrix matrix;
		float x = scales.x;
		float y = scales.y;
		float z = scales.z;
		matrix.m11 = x;
		matrix.m12 = 0;
		matrix.m13 = 0;
		matrix.m14 = 0;
		matrix.m21 = 0;
		matrix.m22 = y;
		matrix.m23 = 0;
		matrix.m24 = 0;
		matrix.m31 = 0;
		matrix.m32 = 0;
		matrix.m33 = z;
		matrix.m34 = 0;
		matrix.m41 = 0;
		matrix.m42 = 0;
		matrix.m43 = 0;
		matrix.m44 = 1;
		return matrix;
	}

	public static Matrix createFromQuaternion( Quaternion quaternion )
	{
		Matrix matrix;
		float num9 = quaternion.x * quaternion.x;
		float num8 = quaternion.y * quaternion.y;
		float num7 = quaternion.z * quaternion.z;
		float num6 = quaternion.x * quaternion.y;
		float num5 = quaternion.z * quaternion.w;
		float num4 = quaternion.z * quaternion.x;
		float num3 = quaternion.y * quaternion.w;
		float num2 = quaternion.y * quaternion.z;
		float num = quaternion.x * quaternion.w;
		matrix.m11 = 1 - ( 2 * ( num8 + num7 ) );
		matrix.m12 = 2 * ( num6 + num5 );
		matrix.m13 = 2 * ( num4 - num3 );
		matrix.m14 = 0;
		matrix.m21 = 2 * ( num6 - num5 );
		matrix.m22 = 1 - ( 2 * ( num7 + num9 ) );
		matrix.m23 = 2 * ( num2 + num );
		matrix.m24 = 0;
		matrix.m31 = 2 * ( num4 + num3 );
		matrix.m32 = 2 * ( num2 - num );
		matrix.m33 = 1 - ( 2 * ( num8 + num9 ) );
		matrix.m34 = 0;
		matrix.m41 = 0;
		matrix.m42 = 0;
		matrix.m43 = 0;
		matrix.m44 = 1;
		return matrix;
	}

	public static Matrix createLookAt( Vector3 cameraPosition, Vector3 cameraTarget, Vector3 cameraUpVector )
	{
		Matrix matrix;
		Vector3 vector = Vector3.normalize( cameraPosition - cameraTarget );
		Vector3 vector2 = Vector3.normalize( Vector3.cross( cameraUpVector, vector ) );
		Vector3 vector3 = Vector3.cross( vector, vector2 );
		matrix.m11 = vector2.x;
		matrix.m12 = vector3.x;
		matrix.m13 = vector.x;
		matrix.m14 = 0f;
		matrix.m21 = vector2.y;
		matrix.m22 = vector3.y;
		matrix.m23 = vector.y;
		matrix.m24 = 0f;
		matrix.m31 = vector2.z;
		matrix.m32 = vector3.z;
		matrix.m33 = vector.z;
		matrix.m34 = 0f;
		matrix.m41 = -Vector3.dot( vector2, cameraPosition );
		matrix.m42 = -Vector3.dot( vector3, cameraPosition );
		matrix.m43 = -Vector3.dot( vector, cameraPosition );
		matrix.m44 = 1f;
		return matrix;
	}
	
	public static Matrix createPerspectiveFieldOfView( float fieldOfView, float aspectRatio, float nearPlaneDistance, float farPlaneDistance )
	{
		Matrix matrix;
		assert( ( fieldOfView > 0 ) && ( fieldOfView < 3.141593 ) );
		assert( nearPlaneDistance > 0 );
		assert( farPlaneDistance > 0 );
		assert( nearPlaneDistance < farPlaneDistance );
		float num = 1f / tan( fieldOfView * 0.5 );
		float num9 = num / aspectRatio;
		matrix.m11 = num9;
		matrix.m12 = 0; 
		matrix.m13 = 0; 
		matrix.m14 = 0;
		matrix.m22 = num;
		matrix.m21 = 0; 
		matrix.m23 = 0; 
		matrix.m24 = 0;
		matrix.m31 = 0;
		matrix.m32 = 0;
		matrix.m33 = farPlaneDistance / ( nearPlaneDistance - farPlaneDistance );
		matrix.m34 = -1f;
		matrix.m41 = 0; 
		matrix.m42 = 0; 
		matrix.m44 = 0;
		matrix.m43 = ( nearPlaneDistance * farPlaneDistance ) / ( nearPlaneDistance - farPlaneDistance );
		return matrix;
	}
	
	public static Matrix createRotationX( float radians )
	{
	    Matrix matrix;
	    float num2 = cos( radians );
	    float num = sin( radians );
	    matrix.m11 = 1f;
	    matrix.m12 = 0f;
	    matrix.m13 = 0f;
	    matrix.m14 = 0f;
	    matrix.m21 = 0f;
	    matrix.m22 = num2;
	    matrix.m23 = num;
	    matrix.m24 = 0f;
	    matrix.m31 = 0f;
	    matrix.m32 = -num;
	    matrix.m33 = num2;
	    matrix.m34 = 0f;
	    matrix.m41 = 0f;
	    matrix.m42 = 0f;
	    matrix.m43 = 0f;
	    matrix.m44 = 1f;
	    return matrix;
	}
	
	public static Matrix createRotationY( float radians )
	{
	    Matrix matrix;
	    float num2 = cos( radians );
	    float num = sin( radians );
	    matrix.m11 = num2;
	    matrix.m12 = 0f;
	    matrix.m13 = -num;
	    matrix.m14 = 0f;
	    matrix.m21 = 0f;
	    matrix.m22 = 1f;
	    matrix.m23 = 0f;
	    matrix.m24 = 0f;
	    matrix.m31 = num;
	    matrix.m32 = 0f;
	    matrix.m33 = num2;
	    matrix.m34 = 0f;
	    matrix.m41 = 0f;
	    matrix.m42 = 0f;
	    matrix.m43 = 0f;
	    matrix.m44 = 1f;
	    return matrix;
	}
	
	public static Matrix createRotationZ( float radians )
	{
	    Matrix matrix;
	    float num2 = cos( radians );
	    float num = sin( radians );
	    matrix.m11 = num2;
	    matrix.m12 = num;
	    matrix.m13 = 0f;
	    matrix.m14 = 0f;
	    matrix.m21 = -num;
	    matrix.m22 = num2;
	    matrix.m23 = 0f;
	    matrix.m24 = 0f;
	    matrix.m31 = 0f;
	    matrix.m32 = 0f;
	    matrix.m33 = 1f;
	    matrix.m34 = 0f;
	    matrix.m41 = 0f;
	    matrix.m42 = 0f;
	    matrix.m43 = 0f;
	    matrix.m44 = 1f;
	    return matrix;
	}
	
	public static Matrix createFromAxisAngle( Vector3 axis, float angle )
	{
		Matrix matrix;
		float x = axis.x;
		float y = axis.y;
		float z = axis.z;
		float num2 = sin( angle );
		float num = cos( angle );
		float num11 = x * x;
		float num10 = y * y;
		float num9 = z * z;
		float num8 = x * y;
		float num7 = x * z;
		float num6 = y * z;
		matrix.m11 = num11 + ( num * ( 1f - num11 ) );
		matrix.m12 = ( num8 - ( num * num8 ) ) + ( num2 * z );
		matrix.m13 = ( num7 - ( num * num7 ) ) - ( num2 * y );
		matrix.m14 = 0f;
		matrix.m21 = ( num8 - ( num * num8 ) ) - ( num2 * z );
		matrix.m22 = num10 + ( num * ( 1f - num10 ) );
		matrix.m23 = ( num6 - ( num * num6 ) ) + ( num2 * x );
		matrix.m24 = 0f;
		matrix.m31 = ( num7 - ( num * num7 ) ) + ( num2 * y );
		matrix.m32 = ( num6 - ( num * num6 ) ) - ( num2 * x );
		matrix.m33 = num9 + ( num * ( 1f - num9 ) );
		matrix.m34 = 0f;
		matrix.m41 = 0f;
		matrix.m42 = 0f;
		matrix.m43 = 0f;
		matrix.m44 = 1f;
		return matrix;
	}

	public Matrix opMul( Matrix matrix )
	{
		Matrix ret;
		ret.m11 = ( ( ( this.m11 * matrix.m11 ) + ( this.m12 * matrix.m21 ) ) + ( this.m13 * matrix.m31 ) ) + ( this.m14 * matrix.m41 );
		ret.m12 = ( ( ( this.m11 * matrix.m12 ) + ( this.m12 * matrix.m22 ) ) + ( this.m13 * matrix.m32 ) ) + ( this.m14 * matrix.m42 );
		ret.m13 = ( ( ( this.m11 * matrix.m13 ) + ( this.m12 * matrix.m23 ) ) + ( this.m13 * matrix.m33 ) ) + ( this.m14 * matrix.m43 );
		ret.m14 = ( ( ( this.m11 * matrix.m14 ) + ( this.m12 * matrix.m24 ) ) + ( this.m13 * matrix.m34 ) ) + ( this.m14 * matrix.m44 );
		ret.m21 = ( ( ( this.m21 * matrix.m11 ) + ( this.m22 * matrix.m21 ) ) + ( this.m23 * matrix.m31 ) ) + ( this.m24 * matrix.m41 );
		ret.m22 = ( ( ( this.m21 * matrix.m12 ) + ( this.m22 * matrix.m22 ) ) + ( this.m23 * matrix.m32 ) ) + ( this.m24 * matrix.m42 );
		ret.m23 = ( ( ( this.m21 * matrix.m13 ) + ( this.m22 * matrix.m23 ) ) + ( this.m23 * matrix.m33 ) ) + ( this.m24 * matrix.m43 );
		ret.m24 = ( ( ( this.m21 * matrix.m14 ) + ( this.m22 * matrix.m24 ) ) + ( this.m23 * matrix.m34 ) ) + ( this.m24 * matrix.m44 );
		ret.m31 = ( ( ( this.m31 * matrix.m11 ) + ( this.m32 * matrix.m21 ) ) + ( this.m33 * matrix.m31 ) ) + ( this.m34 * matrix.m41 );
		ret.m32 = ( ( ( this.m31 * matrix.m12 ) + ( this.m32 * matrix.m22 ) ) + ( this.m33 * matrix.m32 ) ) + ( this.m34 * matrix.m42 );
		ret.m33 = ( ( ( this.m31 * matrix.m13 ) + ( this.m32 * matrix.m23 ) ) + ( this.m33 * matrix.m33 ) ) + ( this.m34 * matrix.m43 );
		ret.m34 = ( ( ( this.m31 * matrix.m14 ) + ( this.m32 * matrix.m24 ) ) + ( this.m33 * matrix.m34 ) ) + ( this.m34 * matrix.m44 );
		ret.m41 = ( ( ( this.m41 * matrix.m11 ) + ( this.m42 * matrix.m21 ) ) + ( this.m43 * matrix.m31 ) ) + ( this.m44 * matrix.m41 );
		ret.m42 = ( ( ( this.m41 * matrix.m12 ) + ( this.m42 * matrix.m22 ) ) + ( this.m43 * matrix.m32 ) ) + ( this.m44 * matrix.m42 );
		ret.m43 = ( ( ( this.m41 * matrix.m13 ) + ( this.m42 * matrix.m23 ) ) + ( this.m43 * matrix.m33 ) ) + ( this.m44 * matrix.m43 );
		ret.m44 = ( ( ( this.m41 * matrix.m14 ) + ( this.m42 * matrix.m24 ) ) + ( this.m43 * matrix.m34 ) ) + ( this.m44 * matrix.m44 );
		return ret;
	}
	
	public string toString()
	{
		string ret;
		ret ~= "[ " ~ str.toString( this.m11 ) ~ ", " ~ str.toString( this.m12 ) ~ ", " ~ str.toString( this.m13 ) ~ ", " ~ str.toString( this.m14 ) ~ " ]\n";
		ret ~= "[ " ~ str.toString( this.m21 ) ~ ", " ~ str.toString( this.m22 ) ~ ", " ~ str.toString( this.m23 ) ~ ", " ~ str.toString( this.m24 ) ~ " ]\n";
		ret ~= "[ " ~ str.toString( this.m31 ) ~ ", " ~ str.toString( this.m32 ) ~ ", " ~ str.toString( this.m33 ) ~ ", " ~ str.toString( this.m34 ) ~ " ]\n";
		ret ~= "[ " ~ str.toString( this.m41 ) ~ ", " ~ str.toString( this.m42 ) ~ ", " ~ str.toString( this.m43 ) ~ ", " ~ str.toString( this.m44 ) ~ " ]\n";
		return ret;
	}
}